﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Security.Principal;
using FlowBasis.Security;
using FlowBasis.Security.Principal;

namespace FlowBasis.Web.Mvc
{
    public class UserAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
    {
        private string[] requiredPermissions;
        private string[] requiredRoles;
        private bool allowUnauthenticatedUsers = false;

        public UserAuthorizeAttribute()
        {
        }

        public string[] RequiredPermissions
        {
            get { return this.requiredPermissions; }
            set { this.requiredPermissions = value; }
        }

        public string[] RequiredRoles
        {
            get { return this.requiredRoles; }
            set { this.requiredRoles = value; }
        }

        public bool AllowUnauthenticatedUsers
        {
            get { return this.allowUnauthenticatedUsers; }
            set { this.allowUnauthenticatedUsers = value; }
        }
        
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            // TODO: Support ChildAction?

            var httpContext = filterContext.HttpContext;
            IPrincipal user = httpContext.User;
            bool isAuthenticated = false;
            if (user != null && user.Identity != null)
            {
                if (user.Identity.IsAuthenticated)
                {
                    isAuthenticated = true;
                }
            }

            bool allowed;
            if (isAuthenticated)
            {
                allowed = true;
            }
            else
            {
                allowed = this.allowUnauthenticatedUsers;
            }

            string missingRole = null;
            string missingPermission = null;

            if (this.requiredRoles != null)
            {
                if (user != null)
                {
                    foreach (string role in this.requiredRoles)
                    {
                        if (!user.IsInRole(role))
                        {
                            allowed = false;
                            missingRole = role;
                            break;
                        }
                    }
                }
                else
                {
                    allowed = false;
                }
            }

            if (this.requiredPermissions != null)
            {
                if (user is IPrincipalWithPermissions)
                {
                    var userWithPermissions = (IPrincipalWithPermissions)user;

                    foreach (string permission in this.requiredPermissions)
                    {
                        if (!userWithPermissions.HasPermission(permission))
                        {
                            allowed = false;
                            missingPermission = permission;
                            break;
                        }
                    }
                }
                else
                {
                    allowed = false;
                }
            }

            if (!allowed)
            {
                var result = new UserUnauthorizedResult();

                try
                {
                    if (missingRole != null)
                        result.MissingRole = missingRole;
                    if (missingPermission != null)
                        result.MissingPermission = missingPermission;
                   
                    if (!isAuthenticated)
                    {
                        string redirectTo = System.Web.Security.FormsAuthentication.LoginUrl 
                            + "?targetUrl=" 
                            + System.Web.HttpUtility.UrlEncode(httpContext.Request.Url.PathAndQuery);

                        result.RedirectTo = redirectTo;
                    }
                }
                finally
                {
                    filterContext.Result = result;                    
                }
            }            
        }

    }


    public class UserUnauthorizedResult : ActionResult
    {        
        public UserUnauthorizedResult()
        {
        }

        public string MissingRole { get; set; }
        public string MissingPermission { get; set; }

        public string RedirectTo { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {                
            var request = context.HttpContext.Request;
            var contentType = request.ContentType;
            if (contentType != null && contentType.IndexOf("application/json") != -1)
            {
                // In case of JSON call, return a PostRpcResult with PermissionDeniedException.

                string errorMessage = "Permission denied";
                
                if (this.MissingPermission != null)
                {
                    errorMessage += ": " + this.MissingPermission;
                }

                if (this.MissingRole != null)
                {
                    errorMessage += ": Role " + this.MissingRole;
                }

                var ex = new PermissionDeniedException(errorMessage)
                {
                    PermissionName = MissingPermission,
                    RoleName = MissingRole
                };

                var postRpcResult = new PostRpcResult(ex);

                postRpcResult.ExecuteResult(context);
            }
            else
            {
                if (this.RedirectTo == null)
                {
                    context.HttpContext.Response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;

                    ViewResult viewResult = new ViewResult();
                    viewResult.ViewName = "~/Views/Shared/UserUnauthorizedResultView.cshtml";
                    viewResult.ViewBag.MissingRole = this.MissingRole;
                    viewResult.ViewBag.MissingPermission = this.MissingPermission;

                    viewResult.ExecuteResult(context);
                }
                else
                {
                    RedirectResult redirectResult = new RedirectResult(this.RedirectTo, false);
                    redirectResult.ExecuteResult(context);
                }
            }
        }
    }
}
